package io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class ZeroCopyDemo {

	/**
	 * 准备一个大文件：30GB
	 * fsutil file createnew d:\30GB.txt 32212254720
	 */
	public static void main(String[] args) throws Exception {

		test_1();
		test_2();
		test_3();
	}

	/**
	 * mmap demo
	 * <p>
	 * mmap基于 OS 的 mmap 的内存映射技术，通过 MMU 映射文件，将文件直接映射到用户态的内存地址，使得对文件的操作不再是write/read，而转化为直接对内存地址的操作，使随机读写文件和读写内存相似的速度。
	 * <p>
	 * 通过mmap可以直接操作和修改文件
	 * <p>
	 * 内存占用很厉害，不知道是不是我写的有毛病（似乎只有fullgc才会回收直接内存）
	 */
	private static void test_1() throws Exception {

		FileChannel readChannel = FileChannel.open(Paths.get("D:\\30GB.txt"), StandardOpenOption.READ);
		FileChannel wrieChannel = FileChannel.open(Paths.get("D:\\30GB_copy.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

		long start = System.currentTimeMillis();
		long size = readChannel.size(); // 剩余总长度
		long len = 0L; // 已处理长度
		while (true) {
			if (size > Integer.MAX_VALUE) {
				wrieChannel.write(readChannel.map(FileChannel.MapMode.READ_ONLY, len, Integer.MAX_VALUE));
				len = len + Integer.MAX_VALUE;
				size = size - Integer.MAX_VALUE;
				System.out.println("剩余大小：" + size);
			} else {
				wrieChannel.write(readChannel.map(FileChannel.MapMode.READ_ONLY, len, size));
				break;
			}
		}
		// mmap拷贝耗时：155818
		System.out.println("mmap拷贝耗时：" + (System.currentTimeMillis() - start));

		readChannel.close();
		wrieChannel.close();
	}

	/**
	 * zero copy demo
	 * <p>
	 * 使用零拷贝的话无法对文件进行修改操作，Linux系统底层使用sendFile，windows也有类似的实现
	 * <p>
	 * 相比下面的传统buff拷贝，零拷贝消耗更少的CPU、占用更少的内存、速度更快
	 */
	private static void test_2() throws Exception {

		FileChannel readChannel = FileChannel.open(Paths.get("D:\\30GB.txt"), StandardOpenOption.READ);
		FileChannel wrieChannel = FileChannel.open(Paths.get("D:\\30GB_copy.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

		long start = System.currentTimeMillis();
		// transferTo方法单次最多处理2GB - 1个字节的长度
		long size = readChannel.size();
		while (size > 0) {
			size = size - readChannel.transferTo(0, size, wrieChannel);
			System.out.println("剩余大小：" + size);
		}
		// 零拷贝耗时：100042
		System.out.println("零拷贝耗时：" + (System.currentTimeMillis() - start));

		readChannel.close();
		wrieChannel.close();
	}

	/**
	 * 传统buffer拷贝，对比测试
	 * <p>
	 * 内存占用没有mmap那么夸张
	 */
	private static void test_3() throws Exception {

		BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("D:\\30GB.txt"));
		BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("D:\\30GB_copy.txt"));

		long start = System.currentTimeMillis();
		byte[] bs = new byte[1024 * 1024 * 1024];
		int len;
		while ((len = inputStream.read(bs)) > 0) {
			System.out.println("reading...writing...");
			outputStream.write(bs, 0, len);
			bs = new byte[1024 * 1024 * 1024];
		}
		// 传统buffer拷贝耗时：200717
		System.out.println("传统buffer拷贝耗时：" + (System.currentTimeMillis() - start));
	}
}
